/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.velocityme.session;

import com.sun.tools.javac.util.Pair;
import com.velocityme.entity.ChangeDelta;
import com.velocityme.entity.Role;
import com.velocityme.entity.State;
import com.velocityme.entity.Status;
import com.velocityme.entity.TaskStateMachine;
import com.velocityme.entity.Transition;
import com.velocityme.enums.ChangeDeltaItemType;
import com.velocityme.enums.ChangeDeltaType;
import com.velocityme.enums.Permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 *
 * @author rcrida
 */
@Stateless
public class TaskStateMachineBean implements TaskStateMachineLocal {
    @EJB
    private NodeLocal nodeBean;

    @PersistenceContext
    private EntityManager em;

    public TaskStateMachine createTaskStateMachine(KeyLocal key, TaskStateMachine taskStateMachine) {
        key.assertHasPermission(taskStateMachine.getParentNode(), Permission.TASK_TYPE_CREATE);
        if (taskStateMachine.getTransitions().size() == 0) {
            taskStateMachine.getTransitions().add(createDefaultTransition());
        }
        ChangeDelta changeDelta = taskStateMachine.addChangeDelta(ChangeDeltaType.CREATE, key.getUser());
        logCreate(taskStateMachine, changeDelta);
        em.persist(taskStateMachine);
        em.persist(changeDelta);
        return taskStateMachine;
    }

    public void editTaskStateMachine(KeyLocal key, TaskStateMachine taskStateMachine) {
        TaskStateMachine current = em.find(TaskStateMachine.class, taskStateMachine.getId());
        key.assertHasPermission(taskStateMachine, Permission.TASK_TYPE_EDIT);
        ChangeDelta changeDelta = taskStateMachine.addChangeDelta(ChangeDeltaType.EDIT, key.getUser());
        logEdit(taskStateMachine, current, changeDelta);
        em.persist(changeDelta);
        em.merge(taskStateMachine);
    }

    public void logCreate(final TaskStateMachine taskStateMachine, ChangeDelta changeDelta) {
        nodeBean.logCreate(taskStateMachine, changeDelta);
        recordRoleChanges(taskStateMachine, new ArrayList<Role>(), changeDelta);
        recordTransitionChanges(taskStateMachine, new ArrayList<Transition>(), changeDelta);
    }

    public void logEdit(final TaskStateMachine taskStateMachine, final TaskStateMachine current, ChangeDelta changeDelta) {
        nodeBean.logEdit(taskStateMachine, current, changeDelta);
        recordRoleChanges(taskStateMachine, current.getInterestedRoles(), changeDelta);
        recordTransitionChanges(taskStateMachine, current.getTransitions(), changeDelta);
    }
    
    private void recordRoleChanges(final TaskStateMachine taskStateMachine,
                                   final Collection<Role> currentInterestedRoles,
                                   ChangeDelta changeDelta) {
        Collection<Role> newRoles = taskStateMachine.getInterestedRoles();
        Collection<Role> addedRoles = new ArrayList<Role>(newRoles);
        addedRoles.removeAll(currentInterestedRoles);
        Collection<Role> removedRoles = new ArrayList<Role>(currentInterestedRoles);
        removedRoles.removeAll(newRoles);

        for (Role role: addedRoles) {
            changeDelta.addItem(ChangeDeltaItemType.ADD_INTERESTED_ROLE, role.toString());
        }
        for (Role role: removedRoles) {
            changeDelta.addItem(ChangeDeltaItemType.REMOVE_INTERESTED_ROLE, role.toString());
        }
    }

     /**
     * Determine how the transitions have changed and set up the value object
     * appropriately so that it performs additions, removals and updates when
     * set.
     *
     * @param p_taskStateMachineValue Contains new values and is updated to reflect changes
     * @param p_transitionLocals The current details in the entity bean
     *
     * @ejb.interface-method view-type="local"
     **/
    private void recordTransitionChanges(final TaskStateMachine taskStateMachine,
                                         final Collection<Transition> currentTransitions,
                                         ChangeDelta changeDelta) {
        Collection<Transition> newTransitions = new ArrayList<Transition>(taskStateMachine.getTransitions());
        Collection<Role> addedRoles = new ArrayList<Role>();
        Collection<Role> removedRoles = new ArrayList<Role>();
        Collection<Pair<Transition,Transition>> updatedTransitions = new ArrayList<Pair<Transition,Transition>>();
        for (Transition originalTransition: new ArrayList<Transition>(currentTransitions)) {
            for (Transition newTransition: new ArrayList<Transition>(newTransitions)) {
                if (em.contains(newTransition)) {
                    // new transition so all roles are new!
                    addedRoles.addAll(newTransition.getResponsibleRoles());
                }
                if (originalTransition.getId().equals(newTransition.getId())) {
                    // calculate the changes to the responsible role for this transition
                    Collection<Role> newRoles = newTransition.getResponsibleRoles();
                    Collection<Role> originalRoles = originalTransition.getResponsibleRoles();
                    addedRoles.addAll(newRoles);
                    addedRoles.removeAll(originalRoles);
                    removedRoles.addAll(originalRoles);
                    removedRoles.removeAll(newRoles);
                    // now compare the transition to the original one
                    if (!originalTransition.equals(newTransition) || addedRoles.size() != 0 || removedRoles.size() != 0) {
                        currentTransitions.remove(originalTransition);
                        newTransitions.remove(newTransition);
                        updatedTransitions.add(new Pair<Transition,Transition>(originalTransition, newTransition));
                    }
                }
            }
        }
        Collection<Transition> addedTransitions = new ArrayList<Transition>(newTransitions);
        addedTransitions.removeAll(currentTransitions);
        Collection<Transition> removedTransitions = new ArrayList<Transition>(currentTransitions);
        removedTransitions.removeAll(newTransitions);

        for (Transition transition: addedTransitions) {
            changeDelta.addItem(ChangeDeltaItemType.ADD_TRANSITION, transition.toString());
            transition.setTaskStateMachine(taskStateMachine);
            em.persist(transition);
        }
        for (Pair<Transition,Transition> transitions: updatedTransitions) {
            StringBuffer strBuffer = new StringBuffer();
            strBuffer.append("{");
            strBuffer.append(transitions.fst.toString());
            strBuffer.append("} -> {");
            strBuffer.append(transitions.snd.toString());
            strBuffer.append("}");
            changeDelta.addItem(ChangeDeltaItemType.UPDATE_TRANSITION, strBuffer.toString());
            em.merge(transitions.snd);
        }
        for (Transition transition: removedTransitions) {
            changeDelta.addItem(ChangeDeltaItemType.REMOVE_TRANSITION, transition.toString());
            em.remove(transition);
        }
    }

    private Transition createDefaultTransition() {
        Transition defaultTransition = new Transition(false, false);
        defaultTransition.setStatus((Status)em.createNamedQuery("findStatusByName").setParameter("name", "Default Status").getSingleResult());
        State startState = (State)em.createNamedQuery("findStartState").getSingleResult();
        defaultTransition.setSourceState(startState);
        defaultTransition.setDestinationState(startState);
        return defaultTransition;
    }

    public Collection<TaskStateMachine> findAllTaskStateMachines(KeyLocal key) {
        key.validateKey();
        return em.createNamedQuery("findAllTaskStateMachines").getResultList();
    }

    public TaskStateMachine findTaskStateMachine(KeyLocal key, Long taskStateMachineId) {
        TaskStateMachine taskStateMachine = em.find(TaskStateMachine.class, taskStateMachineId);
        key.assertHasPermission(taskStateMachine, Permission.NODE_VIEW);
        return taskStateMachine;
    }

}
